
"""
It supports the parse of GTML format with a tree struct.

The parser refers to the obsolete xmllib with the regular expressions. Not \
all xml/gtml can be recognized precisely.
"""

__version__ = "0.01"

__aof__ = ('Marked Language Distruber', 'XML')
__extension__ = (
    ('HTML Files', ('*.htm', '*.html')),
    ('XML Files', ('*.xml',))
)

_BLOCK_SIZE = 100


def _skipSpaceChars(target, source, kws):
    while True:
        str_ = target.readFile(source, _BLOCK_SIZE, **kws)
        for k, c in enumerate(str_):
            if not c.isspace():
                kws['offset'] += k
                return
        else:
            if len(str_) == 0:
                break
            else:
                kws['offset'] += len(str_)


def _getTag(target, source, kws):
    _skipSpaceChars(target, source, kws)
    tag, offset, stop = '', kws['offset'], False
    while not stop:
        str_ = target.readFile(source, _BLOCK_SIZE, **kws)
        for k, c in enumerate(str_):
            if stop or c not in '<-?!%/>':
                tag += str_[:k]
                kws['offset'] += k
                stop = True
                break
            # '>' is the end of the tag
            if c == '>':
                stop = True
        else:
            if len(str_) == 0:
                break
            else:
                tag += str_
                kws['offset'] += len(str_)

    return tag, offset, kws['offset']


def _getName(target, source, kws):
    _skipSpaceChars(target, source, kws)
    name, offset, stop = '', kws['offset'], False
    while not stop:
        str_ = target.readFile(source, _BLOCK_SIZE, **kws)
        for k, c in enumerate(str_):
            if not ('a' <= c <= 'z' or 'A' <= c <= 'Z' or
                    '0' <= c <= '9' or c in '.-_:'):
                name += str_[:k]
                kws['offset'] += k
                stop = True
                break
        else:
            if len(str_) == 0:
                break
            else:
                name += str_
                kws['offset'] += len(str_)

    return name, offset, kws['offset']


def _getEqual(target, source, kws):
    _skipSpaceChars(target, source, kws)
    eq, offset, stop = '', kws['offset'], False
    str_ = target.readFile(source, 1, **kws)
    if str_ == '=':
        eq = '='
        kws['offset'] += 1

    return eq, offset


def _getContentWithEnd(target, source, kws, ending):
    _skipSpaceChars(target, source, kws)
    ret, offset, more = '', kws['offset'], ''

    while True:
        str_ = more + target.readFile(source, _BLOCK_SIZE, **kws)
        k = str_.find(ending)
        if k > -1:
            ret += str_[:k]
            kws['offset'] += k - len(more)
            break
        elif len(str_) == len(more):
            break
        else:
            if len(str_) > len(ending):
                length = len(str_) - len(ending)
            else:
                length = len(str_)

            ret += str_[:length]
            more = str_[length:]
            kws['offset'] += len(str_)

    return ret, offset, kws['offset']


def _getQuoteString(target, source, kws):
    _skipSpaceChars(target, source, kws)
    ret, offset, stop = '', kws['offset'], False
    quote = target.readFile(source, 1, **kws)
    if quote == '"' or quote == "'":
        kws['offset'] += 1

        offset = kws['offset']
        while not stop:
            str_ = target.readFile(source, _BLOCK_SIZE, **kws)
            for k, c in enumerate(str_):
                if c == quote:
                    ret += str_[:k]
                    kws['offset'] += k + 1
                    stop = True
                    break
            else:
                if len(str_) == 0:
                    break
                else:
                    ret += str_
                    kws['offset'] += len(str_)

    return ret, offset, kws['offset']


def _consolateFieldOjbect(target, title, content, kws, sta=-1, off=-1,
                          size=0, fix=False):
    if sta == -1:
        sta = kws.get('start', 0)

    sta += kws.get('offset', 0) if off == -1 else off
    if fix:
        sta -= size

    return target.consolidateFieldObject(
        title, content, start=sta, size=size, attribute='simple')


def _buildElement(stack, target, source, kws):
    if len(stack) < 1:
        return False

    ret = True
    ename, objectp, objstart = stack[-1]

    tag, start, end = _getTag(target, source, kws)
    if tag == '<!--':
        # comments
        obj = _consolateFieldOjbect(
            target, 'comment', '', kws, size=4, fix=True)

        comment, stc, ec = _getContentWithEnd(target, source, kws, '-->')
        if comment:
            obj.append(
                _consolateFieldOjbect(
                    target, 'comment', comment, kws, off=stc,
                    size=len(comment)))

        tag, ste, end = _getTag(target, source, kws)
        ret = tag == '-->'
        if ret:
            obj.adjust(size=end - start)
            obj.update(content=comment)
            objectp.appendChildren(obj)
    elif tag == '<%':
        content, stc, ec = _getContentWithEnd(target, source, kws, '%>')
        tag, ste, end = _getTag(target, source, kws)
        ret = tag == '%>'
        if content and ret:
            objectp.appendChildren(
                _consolateFieldOjbect(
                    target, '%%', content, kws, off=start, size=end - start))
    elif tag == '<?' or tag == '<':
        # CDATA isn't handled
        objp = _consolateFieldOjbect(
            target, 'element', '', kws, size=len(tag), fix=True)

        tname, ste, enn = _getName(target, source, kws)
        ret = len(tname) > 0
        if ret:
            objp.appendChildren(
                _consolateFieldOjbect(
                    target, 'name', tname, kws, size=len(tname), fix=True))
            objp.update(content=tname)

        while ret:
            name, stn, enn = _getName(target, source, kws)
            if name:
                objn = _consolateFieldOjbect(
                    target, 'attribute', '', kws, size=len(name), fix=True)
                objn.appendChildren(
                    _consolateFieldOjbect(
                        target, 'name', name, kws, size=len(name), fix=True))
            else:
                break

            eq, offe = _getEqual(target, source, kws)
            ret = eq == '='
            if not ret:
                break

            val, stv, env = _getQuoteString(target, source, kws)
            # confirm if it's an empty string
            if val or stv != env:
                objn.appendChildren(
                    _consolateFieldOjbect(
                        target, 'value', val, kws, off=stv, size=len(val)))
                objn.adjust(size=env - stn)
                objn.update(content='%s=%s' % (name, val))
                objp.appendChildren(objn)
            else:
                ret = False

        etag, stt, ent = _getTag(target, source, kws)
        if (tag == '<' and etag == '/>') or (tag == '<?' and etag == '?>'):
            objp.adjust(size=ent - start)
            objectp.appendChildren(objp)
        elif tag == '<' and etag == '>':
            stack.append((tname, objp, start))
            objectp.appendChildren(objp)
        else:
            ret = False
    elif tag == '</':
        if len(stack) > 0:
            name, stn, enn = _getName(target, source, kws)
            ret = name == ename
            if ret:
                tag, stt, ent = _getTag(target, source, kws)
                ret = tag == '>'
                if ret:
                    stack.pop(-1)
                    objectp.adjust(size=ent - objstart)
        else:
            ret = False
    else:
        val, stv, env = _getContentWithEnd(target, source, kws, '<')
        if len(stack) > 1 and len(val) > 0 and len(stack) > 0:
            objectp.appendChildren(
                _consolateFieldOjbect(
                    target, 'value', val, kws, size=len(val), fix=True))
        elif stv != env:
            ret = False

    return ret


class _XmlParser:
    def Execute(self, source, target, notify, **kws):
        entry = target.getEntry()

        ret, stack = False, list([('', entry, kws.get('start', 0))])
        while kws['offset'] < len(source):
            ret = _buildElement(stack, target, source, kws)
            if not ret:
                break

        return ret and entry.hasChildren()


def fmtService():
    return _XmlParser
